<?php

require_once('logger.php');
require_once('packet.php');
require_once('sxml2array.php');


function    query_snap($gate_url, $urlmd5)
{
    $snaper = new Snaper($gate_url);

    $time_start = microtime(true);

    $data = $snaper->queryByMD5($urlmd5);
    $result = False;

    $time_last = microtime(true) - $time_start;
    $logger = getLogger();
    if (empty($data)) {
        $logger->info(sprintf("status:fail urlmd5:%s time:%d",$urlmd5, intval($time_last * 1000)));
    }
    else {
        $logger->info(sprintf("status:ok urlmd5:%s time:%d",$urlmd5, intval($time_last * 1000)));

        $sxml = gzuncompress($data);
        if (!empty($sxml)) {
            $res = sxml2array($sxml);
            if (!empty($res)) {
                $result = serialize($res);
            }
        }
    }

    return $result;
}


class    RuntimeError extends Exception
{
}   // class RuntimeError


class   Snaper
{
    private $gate_url = false;
    private $gate_host = false;
    private $gate_port = false;

    const _END_MARK = "\r\n\r\n";
    const _END_MARK_SIZE = 4;

    const RECV_BUF_SIZE   = 1048576;

    private $recv_buf;

    public function __construct($gate_url)
    {
        $this->gate_url = $gate_url;

        $gate_info = parse_url($gate_url);
        $this->gate_host = $gate_info['host'];
        $this->gate_port = $gate_info['port'];

        $this->recv_buf = '';


        $this->logger = getLogger();
        $this->socket = False;
    }

    public function __destruct()
    {
        if (!empty($this->socket)) {
            $this->destroyChannel($this->socket);
        }
    }


    public function getChannel()
    {
        if (empty($this->socket)) {
            try {
                $this->socket = $this->createChannel($this->gate_host, $this->gate_port);
            }
            catch(Exception $ex) {
                return False;
            }
        }

        return $this->socket;
    }


    public function createChannel($gate_host, $gate_port)
    {   
        $this->logger->debug(sprintf("connecting to gate-server %s:%s ...\n", $gate_host, $gate_port));
        list($ip, $port) = array($gate_host, $gate_port);
        $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        $ok = socket_connect($sock, $ip, $port);
        if (!$ok) {
            socket_close($sock);

            $errorstr = sprintf("Error connect socket to (%s:%s) -- [%d] %s", $ip, $port, socket_last_error(), socket_strerror(socket_last_error()));
            $this->logger->error($errorstr);
    
            throw new RuntimeError($errorstr);
        }
    
        $channel = $sock;
        return $channel;
    }   
    
    public function destroyChannel($channel)
    {
        socket_close($channel);
    }

    
    public function queryByMD5($urlmd5)
    {
        // STEP. query url by url-md5
        $url = $this->__getUrlByMD5($urlmd5);
        if (empty($url)) {
            $errorstr = sprintf('Fail to get URL by MD5 "%s"', $urlmd5);
            $this->logger->error($errorstr);
            return False;
        }

        // STEP. query data by url
        $data = $this->__getDataByUrl($url);
        if (empty($data)) {
            $errorstr = sprintf('Fail to get DATA by URL "%s"', $url);
            $this->logger->error($errorstr);
            return False;
        }

        return $data; 
    }

    public function queryByURL($url)
    {
        // STEP. query data by url
        $data = $this->__getDataByUrl($url);
        if (empty($data)) {
            $errorstr = sprintf('Fail to get DATA by URL "%s"', $url);
            $this->logger->error($errorstr);
            return False;
        }

        return $data; 
    }


    public function __getUrlByMD5($urlmd5)
    {
        // STEP. make connection to gate-server
        $channel = $this->getChannel();
        if (empty($channel)) {
            $errorstr = 'Fail to get channel';
            $this->logger->error($errorstr);
            return False;
        }
            
        // STEP. make request packet
        $packet = new Packet();
        $packet->setRequest();
        $packet->table = 'news.md5';
        $packet->op = Packet::OP_FIND;
        $packet->key = $urlmd5;

        // STEP. send request packet
        $this->logger->debug(sprintf('sending request to get url by md5 "%s" ...', $urlmd5));
        $timeout = 1;
        try {
            $request_id = $this->sendPacket($packet, $timeout);
        }
        catch(Exception $ex) {
            $this->logger->error($ex->getMessage());
            return False;
        }   

        // STEP. recv response packet
        $this->logger->debug(sprintf('waiting response to get url by md5 "%s" ...', $urlmd5));
        try {
            $response_packet = $this->recvPacket($request_id, $timeout);
        }
        catch(Exception $ex) {
            $this->logger->error($ex->getMessage());
            return False;
        }   

        return $response_packet->data;
    }

    public function __getDataByUrl($url)
    {
        // STEP. make connection to gate-server
        $channel = $this->getChannel();
        if (empty($channel)) {
            $errorstr = 'Fail to get channel';
            $this->logger->error($errorstr);
            return False;
        }
            
        // STEP. make request packet
        $packet = new Packet();
        $packet->setRequest();
        $packet->table = 'news.data';
        $packet->op = Packet::OP_FIND;
        $packet->key = $url;

        // STEP. send request packet
        $this->logger->debug(sprintf('sending request to get data by url "%s" ...', $url));
        $timeout = 1;
        try {
            $request_id = $this->sendPacket($packet, $timeout);
        }
        catch(Exception $ex) {
            $this->logger->error($ex->getMessage());
            return False;
        }   

        // STEP. recv response packet
        $this->logger->debug(sprintf('waiting response to get data by url "%s" ...', $url));
        try {
            $response_packet = $this->recvPacket($request_id, $timeout);
        }
        catch(Exception $ex) {
            $this->logger->error($ex->getMessage());
            return False;
        }   

        return $response_packet->data;
    }

    public function sendPacket($packet, $timeout)
    {
        list($request_str,$request_id) = $packet->enpack();
        $send_buf = join('',array($request_str, self::_END_MARK));

        socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => $timeout, 'usec' => 0) );

        while (strlen($send_buf) > 0) {
            $ret = socket_write($this->socket, $send_buf);
            if (FALSE === $ret) {
                $errno = socket_last_error($this->socket);
                //if ((111 == $errno) || (104 == $errno)) { do something }
                $msg = sprintf('Socket WRITE Error -- [%d] %s', $errno, socket_strerror($errno));
                throw new Exception($msg, $errno);
            }
            $send_buf = substr($send_buf, $ret);
            //printf("SEND size=$ret\n");
        }

        return $request_id;
    }

    public function recvPacket($request_id, $timeout)
    {
        $pos_scan = strlen($this->recv_buf) - self::_END_MARK_SIZE;
        if ($pos_scan < 0) {
            $pos_scan = 0;
        }

        socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $timeout, 'usec' => 0) );

        while(true) {
            $pos = strpos($this->recv_buf, self::_END_MARK, $pos_scan);
            if (FALSE === $pos) {
                $ret = socket_read($this->socket, self::RECV_BUF_SIZE);
                //$ret = socket_read($this->socket, self::RECV_BUF_SIZE, PHP_NORMAL_READ);
                if (false === $ret) {
                    $errno = socket_last_error($this->socket);
                    //if ((111 == $errno) || (104 == $errno)) { do something }
                    $msg = sprintf('Socket READ Error -- [%d] %s', $errno, socket_strerror($errno));
                    throw new Exception($msg, $errno);
                }

                $pos_scan = strlen($this->recv_buf) - self::_END_MARK_SIZE;
                if ($pos_scan < 0) {
                    $pos_scan = 0;
                }
                $this->recv_buf = join('', array($this->recv_buf, $ret));
                //$this->recv_buf = $this->recv_buf . $ret;
            }
            else {
                $data = substr($this->recv_buf, 0, $pos);
                $this->recv_buf = substr($this->recv_buf, $pos + self::_END_MARK_SIZE );

                //var_dump($data);
                $packet = depack($data);
                //var_dump($packet);
                if (empty($request_id) || ($request_id == $packet->respid)) {
                    break;
                }
                else {
                    $data = '';
                }
            }
        }

        return $packet;
    }

}


